﻿#region COPYRIGHT© 2009-2012 Phillip Clark. All rights reserved.

// For licensing information see License.txt (MIT style licensing).

#endregion

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Threading;
using FlitBit.Core.Parallel;

namespace FlitBit.Cache
{
	/// <summary>
	///   Abstract base class implementation of ICacheItemResolver&lt;K,R>
	/// </summary>
	/// <typeparam name="TKey">key type K</typeparam>
	/// <typeparam name="TValue">item type V</typeparam>
	public abstract class CacheItemResolver<TKey, TValue> : ICacheItemResolver<TKey, TValue>
	{
		int _resolveAttempts;
		int _resolveSuccesses;

		/// <summary>
		///   Overriden by subclasses to try a single item.
		/// </summary>
		/// <param name="key">the item's key</param>
		/// <param name="continuation">a continuation that the implementation must call when complete.</param>
		protected abstract void PerformTryResolve(TKey key, Continuation<Tuple<ResolutionKinds, TKey, TValue>> continuation);

		/// <summary>
		///   Increments the counter tracking resolve attempts.
		/// </summary>
		protected virtual void IncrementResolveAttemptCounter()
		{
			Interlocked.Increment(ref _resolveAttempts);
		}

		/// <summary>
		///   Increments the counter tracking resolve attempts.
		/// </summary>
		/// <param name="increment"></param>
		protected virtual void IncrementResolveAttemptCounter(int increment)
		{
			Interlocked.Add(ref _resolveAttempts, increment);
		}

		/// <summary>
		///   Increments the counter tracking successful resolve attempts.
		/// </summary>
		protected virtual void IncrementResolveSuccessCounter()
		{
			Interlocked.Increment(ref _resolveSuccesses);
		}

		/// <summary>
		///   Increments the counter tracking successful resolve attempts.
		/// </summary>
		/// <param name="increment"></param>
		protected virtual void IncrementResolveSuccessCounter(int increment)
		{
			Interlocked.Add(ref _resolveSuccesses, increment);
		}

		/// <summary>
		///   Tries to resolve many items missing from the cache by delegating to the singular resolve.
		/// </summary>
		/// <param name="keys">the item's key</param>
		/// <param name="continuation">a continuation that the implementation must call when complete.</param>
		protected virtual void PerformTryResolveMany(IEnumerable<TKey> keys,
			Continuation<IEnumerable<Tuple<ResolutionKinds, TKey, TValue>>> continuation)
		{
			Contract.Requires<ArgumentNullException>(keys != null);

			var accum = new ConcurrentBag<Tuple<ResolutionKinds, TKey, TValue>>();
			var errors = 0;
			var kk = keys as TKey[] ?? keys.ToArray();
			var callbacks = kk.Count();
			Continuation<Tuple<ResolutionKinds, TKey, TValue>> singular = (e, res) =>
			{
				Interlocked.Decrement(ref callbacks);
				if (e != null)
				{
					if (Interlocked.CompareExchange(ref errors, 1, 0) == 0)
					{
						continuation(e, default(IEnumerable<Tuple<ResolutionKinds, TKey, TValue>>));
					}
				}
				else if (res != null)
				{
					accum.Add(res);
				}
			};
			try
			{
				foreach (var k in kk)
				{
					PerformTryResolve(k, singular);
				}
				// Wait until all items are accounted for or until we error.
				while (Thread.VolatileRead(ref errors) == 0 && Thread.VolatileRead(ref callbacks) > 0)
				{
					Thread.Sleep(0);
				}
			}
			catch (Exception e)
			{
				continuation(e, null);
				return;
			}
			if (Thread.VolatileRead(ref errors) == 0)
			{
				continuation(null, accum);
			}
		}

		#region ICacheItemResolver<K,V> Members

		/// <summary>
		///   Gets the number of items attempted.
		/// </summary>
		public int AttemptCount { get { return Thread.VolatileRead(ref _resolveAttempts); } }

		/// <summary>
		///   Gets the number of items resolved.
		/// </summary>
		public int ResolveCount { get { return Thread.VolatileRead(ref _resolveSuccesses); } }

		/// <summary>
		///   Tries to resolve an item missing from the cache.
		/// </summary>
		/// <param name="key">the item's key</param>
		/// <param name="continuation">A continuation where the result is returned upon completion.</param>
		public void TryResolve(TKey key, Continuation<Tuple<ResolutionKinds, TKey, TValue>> continuation)
		{
			PerformTryResolve(key, continuation);
		}

		/// <summary>
		///   Tries to resolve many items missing from the cache.
		/// </summary>
		/// <param name="keys">the item's key</param>
		/// <param name="continuation">A continuation where the results are returned upon completion.</param>
		public void TryResolveMany(IEnumerable<TKey> keys, Continuation<IEnumerable<Tuple<ResolutionKinds, TKey, TValue>>> continuation)
		{
			PerformTryResolveMany(keys, continuation);
		}

		#endregion
	}
}